﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Granite.Collections;
using Granite.Commons.Tests.Mocks;
using Granite.ComponentModel;
using Granite.Eventing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Runtime.Serialization;

namespace Granite.Commons.Tests.Collections
{
	[TestClass]
	public class ExtendedObservableCollectionTests
	{
		[TestMethod]
		public void ExtendedObservableCollection_ConstructorTest1()
		{
			var result = new ExtendedObservableCollection<object>();
			result.Add(1);
			result.Add("asas");

			var weakNotifier = new WeakNotifier();
			var notifier = new Notifier();

			result.Add(weakNotifier);
			result.Add(notifier);

			ConstructorAssertions(result, weakNotifier, notifier);
		}


		[TestMethod]
		public void ExtendedObservableCollection_ConstructorTest2()
		{
			var weakNotifier = new WeakNotifier();
			var notifier = new Notifier();
			var temp = new object[] { notifier, weakNotifier };


			var result = new ExtendedObservableCollection<object>(temp.ToList());

			ConstructorAssertions(result, weakNotifier, notifier);
		}

		[TestMethod]
		public void ExtendedObservableCollection_ConstructorTest3()
		{
			var weakNotifier = new WeakNotifier();
			var notifier = new Notifier();
			var temp = new[] { notifier, weakNotifier };


			var result = new ExtendedObservableCollection<object>(temp.AsEnumerable());

			ConstructorAssertions(result, weakNotifier, notifier);
		}


		[TestMethod]
		public void ExtendedObservableCollection_AddRange_Test1()
		{
			var weakNotifier = new WeakNotifier();
			var notifier = new Notifier();
			var temp = new[] { notifier, weakNotifier };


			var result = new ExtendedObservableCollection<object>();
			result.AddRange(temp);
			ConstructorAssertions(result, weakNotifier, notifier);
		}

		[TestMethod]
		public void ExtendedObservableCollection_AddRange_Test2()
		{
			try
			{
				var result = new ExtendedObservableCollection<object>();
				result.AddRange(null);
				Assert.Fail("Expected an ArgumentNullException exception");
			}
			catch (ArgumentNullException ex)
			{
				Assert.AreEqual("list", ex.ParamName, "Parameter name is incorrect");
			}
		}

		private static void ConstructorAssertions(ExtendedObservableCollection<object> result, WeakNotifier weakNotifier, Notifier notifier)
		{
			var itemAssert = new ItemPropertyChangedEventAssert(result);

			var itemAddedEventQueue = new Queue<Tuple<object, ItemEventArgs<object>>>();
			result.ItemAdded += (s, e) => itemAddedEventQueue.Enqueue(Tuple.Create(s, e));

			var itemRemovedEventQueue = new Queue<Tuple<object, ItemEventArgs<object>>>();
			result.ItemRemoved += (s, e) => itemRemovedEventQueue.Enqueue(Tuple.Create(s, e));


			weakNotifier.Age += 1;
			itemAssert.Expect(weakNotifier, "Age");

			notifier.Age += 1;
			itemAssert.Expect(notifier, "Age");


			var oldCount = result.Count;
			result.Clear();
			Assert.AreEqual(oldCount, itemRemovedEventQueue.Count);

			weakNotifier.Age += 1;
			notifier.Age += 1;

			Assert.AreEqual(0, result.Count, "The collection should be empty");
			itemAssert.ExpectCountEquals(0, "ItemPropertyChanged events were fired when the collection was supposed to be empty.");

			result.Add(weakNotifier);
			result.Add(notifier);
			Assert.AreEqual(2, result.Count);
			Assert.AreEqual(2, itemAddedEventQueue.Count);
			CollectionAssert.AreEqual(result, result.ReadOnlyWrapper);

			itemRemovedEventQueue.Clear();
			result.RemoveAt(0);
			Assert.AreEqual(1, itemRemovedEventQueue.Count);
			Assert.AreSame(notifier, result[0]);
			CollectionAssert.AreEqual(result, result.ReadOnlyWrapper);

			itemAddedEventQueue.Clear();
			itemRemovedEventQueue.Clear();
			result[0] = weakNotifier;
			Assert.AreSame(weakNotifier, result[0]);
			Assert.AreEqual(1, itemAddedEventQueue.Count);
			Assert.AreEqual(1, itemRemovedEventQueue.Count);
			CollectionAssert.AreEqual(result, result.ReadOnlyWrapper);
		}



		[TestMethod]
		public void ExtendedObservableCollection_MemroryTest1()
		{
			var result = new ExtendedObservableCollection<object>();
			var weakNotifier = new WeakNotifier();
			result.Add(weakNotifier);

			var wr = new WeakReference(result);

			result = null;

			TestUtilities.GCCycle();
			Assert.IsFalse(wr.IsAlive, "An item in the collection is preventing the collection from being collected");
		}

		[TestMethod]
		public void ExtendedObservableCollection_PropertyChangedListenerTest()
		{
			var result = new FooCollection();
			var propertyChangedEventQueue = new Queue<Tuple<object, PropertyChangedEventArgs>>();
			var propertyChangedListener = new Listener<PropertyChangedEventArgs>((s, e) => propertyChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.AddHandler(propertyChangedListener);

			result.Boom += 1;
			Assert.AreEqual(1, propertyChangedEventQueue.Count);
			var event1 = propertyChangedEventQueue.Dequeue();
			Assert.AreSame(result, event1.Item1);
			Assert.AreEqual("Boom", event1.Item2.PropertyName);

			result.RemoveHandler(propertyChangedListener);
			result.Boom += 1;
			Assert.AreEqual(0, propertyChangedEventQueue.Count);
		}

		[TestMethod]
		public void ExtendedObservableCollection_ItemPropertyChangedListenerTest1()
		{
			var result = new FooCollection();
			var itemPropertyChangedEventQueue = new Queue<Tuple<object, RelayedEventArgs<PropertyChangedEventArgs>>>();
			var itemPropertyChangedListener = new Listener<RelayedEventArgs<PropertyChangedEventArgs>>((s, e) => itemPropertyChangedEventQueue.Enqueue(Tuple.Create(s, e)));

			var mutator = new Foo();
			result.Add(mutator);

			result.AddHandler(itemPropertyChangedListener);
			mutator.FooBar = "AAA";

			Assert.AreEqual(1, itemPropertyChangedEventQueue.Count);
			var event1 = itemPropertyChangedEventQueue.Dequeue();
			Assert.AreSame(result, event1.Item1);
			Assert.AreEqual(mutator, event1.Item2.OriginalSender);
			Assert.AreEqual("FooBar", event1.Item2.EventArgs.PropertyName);

			result.RemoveHandler(itemPropertyChangedListener);

			mutator.FooBar = "BBB";

			Assert.AreEqual(0, itemPropertyChangedEventQueue.Count);
		}

		[TestMethod]
		public void ExtendedObservableCollection_ItemPropertyChangedListenerTest2()
		{
			var result = new FooCollection();
			var itemPropertyChangedEventQueue = new Queue<Tuple<object, RelayedEventArgs<PropertyChangedEventArgs>>>();
			var itemPropertyChangedListener = new Listener<RelayedEventArgs<PropertyChangedEventArgs>>((s, e) => itemPropertyChangedEventQueue.Enqueue(Tuple.Create(s, e)));

			var mutator = new Foo();
			result.Add(mutator);

			result.AddHandler(itemPropertyChangedListener);
			mutator.FooBar = "AAA";

			Assert.AreEqual(1, itemPropertyChangedEventQueue.Count);
			var event1 = itemPropertyChangedEventQueue.Dequeue();
			Assert.AreSame(result, event1.Item1);
			Assert.AreEqual(mutator, event1.Item2.OriginalSender);
			Assert.AreEqual("FooBar", event1.Item2.EventArgs.PropertyName);

			result.Remove(mutator);

			mutator.FooBar = "BBB";

			Assert.AreEqual(0, itemPropertyChangedEventQueue.Count);
		}


		[TestMethod]
		public void ExtendedObservableCollection_CollectionChangedListenerTest()
		{
			var result = new FooCollection();
			var collectionChangedEventQueue = new Queue<Tuple<object, NotifyCollectionChangedEventArgs>>();
			var collectionChangedListener = new Listener<NotifyCollectionChangedEventArgs>((s, e) => collectionChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.AddHandler(collectionChangedListener);

			result.Add(new Foo());
			Assert.AreEqual(1, collectionChangedEventQueue.Count);
			var event1 = collectionChangedEventQueue.Dequeue();
			Assert.AreSame(result, event1.Item1);
			Assert.AreEqual(NotifyCollectionChangedAction.Add, event1.Item2.Action);

			result.RemoveHandler(collectionChangedListener);
			result.Add(new Foo());
			Assert.AreEqual(0, collectionChangedEventQueue.Count);
		}

		/// <summary>
		/// This exercises the code path where we remove a listener without first adding one.
		/// </summary>
		[TestMethod]
		public void ExtendedObservableCollection_RemoveHandlerTest()
		{
			var result = new FooCollection();
			var collectionChangedEventQueue = new Queue<Tuple<object, NotifyCollectionChangedEventArgs>>();
			var collectionChangedListener = new Listener<NotifyCollectionChangedEventArgs>((s, e) => collectionChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.RemoveHandler(collectionChangedListener);

			var propertyChangedEventQueue = new Queue<Tuple<object, PropertyChangedEventArgs>>();
			var propertyChangedListener = new Listener<PropertyChangedEventArgs>((s, e) => propertyChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.RemoveHandler(propertyChangedListener);


			var itemPropertyChangedEventQueue = new Queue<Tuple<object, RelayedEventArgs<PropertyChangedEventArgs>>>();
			var itemPropertyChangedListener = new Listener<RelayedEventArgs<PropertyChangedEventArgs>>((s, e) => itemPropertyChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.RemoveHandler(itemPropertyChangedListener);
		}

		/// <summary>
		/// This exercises the code path where we remove a listener twice
		/// </summary>
		[TestMethod]
		public void ExtendedObservableDictionary_RemoveHandlerTest2()
		{
			var result = new FooCollection();
			var collectionChangedEventQueue = new Queue<Tuple<object, NotifyCollectionChangedEventArgs>>();
			var collectionChangedListener = new Listener<NotifyCollectionChangedEventArgs>((s, e) => collectionChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.AddHandler(collectionChangedListener);
			result.RemoveHandler(collectionChangedListener);
			result.RemoveHandler(collectionChangedListener);

			var propertyChangedEventQueue = new Queue<Tuple<object, PropertyChangedEventArgs>>();
			var propertyChangedListener = new Listener<PropertyChangedEventArgs>((s, e) => propertyChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.AddHandler(propertyChangedListener);
			result.RemoveHandler(propertyChangedListener);
			result.RemoveHandler(propertyChangedListener);


			var itemPropertyChangedEventQueue = new Queue<Tuple<object, RelayedEventArgs<PropertyChangedEventArgs>>>();
			var itemPropertyChangedListener = new Listener<RelayedEventArgs<PropertyChangedEventArgs>>((s, e) => itemPropertyChangedEventQueue.Enqueue(Tuple.Create(s, e)));
			result.AddHandler(itemPropertyChangedListener);
			result.RemoveHandler(itemPropertyChangedListener);
			result.RemoveHandler(itemPropertyChangedListener);
		}

		[TestMethod]
		public void ExtendedObservableCollection_AddNullHandlerTest()
		{
			var result = new FooCollection();

			try
			{
				result.AddHandler((IListener<NotifyCollectionChangedEventArgs>)null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentNullException ex)
			{
				Assert.AreEqual("eventHandler", ex.ParamName);
			}

			try
			{
				result.AddHandler((IListener<PropertyChangedEventArgs>)null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentNullException ex)
			{
				Assert.AreEqual("eventHandler", ex.ParamName);
			}

			try
			{
				result.AddHandler((IListener<RelayedEventArgs<PropertyChangedEventArgs>>)null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentNullException ex)
			{
				Assert.AreEqual("eventHandler", ex.ParamName);
			}
		}

		[TestMethod]
		public void ExtendedObservableCollection_RemoveNullHandlerTest()
		{
			var result = new FooCollection();

			try
			{
				result.RemoveHandler((IListener<NotifyCollectionChangedEventArgs>)null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentNullException ex)
			{
				Assert.AreEqual("eventHandler", ex.ParamName);
			}

			try
			{
				result.RemoveHandler((IListener<PropertyChangedEventArgs>)null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentNullException ex)
			{
				Assert.AreEqual("eventHandler", ex.ParamName);
			}

			try
			{
				result.RemoveHandler((IListener<RelayedEventArgs<PropertyChangedEventArgs>>)null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentNullException ex)
			{
				Assert.AreEqual("eventHandler", ex.ParamName);
			}
		}

		[TestMethod]
		public void ExtendedObservableCollection_RemoveItemFailTest1()
		{
			var result = new FooCollection();

			try
			{
				result.InvokeRemoveItem(-1);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentOutOfRangeException ex)
			{
				Assert.AreEqual("index", ex.ParamName);
				Assert.AreEqual(-1, ex.ActualValue);
			}
		}

		[TestMethod]
		public void ExtendedObservableCollection_RemoveItemFailTest2()
		{
			var result = new FooCollection();

			try
			{
				result.InvokeRemoveItem(0);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentOutOfRangeException ex)
			{
				Assert.AreEqual("index", ex.ParamName);
				Assert.AreEqual(0, ex.ActualValue);
			}
		}

		[TestMethod]
		public void ExtendedObservableCollection_SetItemFailTest1()
		{
			var result = new FooCollection();

			try
			{
				result.InvokeSetItem(-1, null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentOutOfRangeException ex)
			{
				Assert.AreEqual("index", ex.ParamName);
				Assert.AreEqual(-1, ex.ActualValue);
			}
		}

		[TestMethod]
		public void ExtendedObservableCollection_SetItemFailTest2()
		{
			var result = new FooCollection();

			try
			{
				result.InvokeSetItem(0, null);
				Assert.Fail("Expected an exception");
			}
			catch (ArgumentOutOfRangeException ex)
			{
				Assert.AreEqual("index", ex.ParamName);
				Assert.AreEqual(0, ex.ActualValue);
			}
		}

		[TestMethod]
		public void ExtendedObservableCollection_AddRemoveOverrideTest()
		{
			var result = new FooCollection(new[] { new Foo(), new Foo(), new Foo() });
			Assert.AreEqual(3, result.AddCount);
		}


		[TestMethod]
		public void ExtendedObservableCollection_MixedAddRemoveTest()
		{
			var fired = 0;
			var result = new FooCollection();
			PropertyChangedEventHandler eventHandler = (s, e) => fired += 1;


			result.PropertyChanged += eventHandler;
			result.Add(new Foo());
			Assert.AreEqual(2, fired); //Count and Item[]

			((INotifyPropertyChanged)result).PropertyChanged -= eventHandler;
			result.Add(new Foo());

			Assert.AreEqual(2, fired); //Count and Item[]

		}

		[TestMethod]
		public void ExtendedObservableCollection_SerializationTest1()
		{
			var collection = new ExtendedObservableCollection<Foo>();
			collection.Add(new Foo());
			collection.Add(new Foo());
			collection.Add(new Foo());
			
			var stream = new System.IO.MemoryStream();
			var fooSerializer = new DataContractSerializer(typeof(ExtendedObservableCollection<Foo>));
			fooSerializer.WriteObject(stream, collection);
			stream.Position = 0;
			var newFoo = (ExtendedObservableCollection<Foo>)fooSerializer.ReadObject(stream);

			Assert.AreEqual(collection.Count, newFoo.Count);

		}

		[TestMethod]
		public void ExtendedObservableCollection_SerializationTest2()
		{
			var collection = new FooCollection();
			collection.Add(new Foo());
			collection.Add(new Foo());
			collection.Add(new Foo());

			var stream = new System.IO.MemoryStream();
			var fooSerializer = new DataContractSerializer(typeof(FooCollection));
			fooSerializer.WriteObject(stream, collection);
			stream.Position = 0;
			var newFoo = (FooCollection)fooSerializer.ReadObject(stream);

			Assert.AreEqual(collection.Count, newFoo.Count);

		}

	}
}

